package org.leafstar.shortlink.admin.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.leafstar.shortlink.admin.common.convention.errorcode.BaseErrorCode;
import org.leafstar.shortlink.admin.common.convention.exception.ClientException;
import org.leafstar.shortlink.admin.common.convention.exception.ServiceException;
import org.leafstar.shortlink.admin.dao.entity.UserDo;
import org.leafstar.shortlink.admin.dao.mapper.UserMapper;
import org.leafstar.shortlink.admin.dto.req.UserLoginReqDTO;
import org.leafstar.shortlink.admin.dto.req.UserRegisterReqDTO;
import org.leafstar.shortlink.admin.dto.req.UserUpdateReqDTO;
import org.leafstar.shortlink.admin.dto.resp.UserLoginRespDTO;
import org.leafstar.shortlink.admin.dto.resp.UserRespDTO;
import org.leafstar.shortlink.admin.service.GroupService;
import org.leafstar.shortlink.admin.service.UserService;
import org.leafstar.shortlink.admin.util.BeanUtil;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static org.leafstar.shortlink.admin.common.constant.RedisCacheConstant.LOCK_USER_REGISTER_KEY;

/**
 * 用户接口实现
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDo> implements UserService {
    private final RBloomFilter<String> userRegisterBloomFilter;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final GroupService groupService;

    public UserRespDTO getUserByName(String username) {
        UserDo userDo = baseMapper.selectOne(new LambdaQueryWrapper<UserDo>().eq(UserDo::getUsername, username));
        //空值直接抛出异常，非空值转化后返回
        return Optional.ofNullable(userDo).map(u -> {
            return BeanUtil.convert(u, new UserRespDTO());
        }).orElseThrow(() -> new ClientException(BaseErrorCode.CLIENT_ERROR));
    }

    @Override
    public boolean hasUsername(String username) {
        return userRegisterBloomFilter.contains(username);
    }

    @Override
    public void Register(UserRegisterReqDTO registerReqDTO) {
        if (hasUsername(registerReqDTO.getUsername())) {
            throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
        }
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + registerReqDTO.getUsername());
        try {
            if (lock.tryLock()) {
                int insert = baseMapper.insert(BeanUtil.convert(registerReqDTO, UserDo.class));
                if (insert < 1) {
                    throw new ServiceException(BaseErrorCode.USER_REGISTER_ERROR);
                }
                //将用户名添加到布隆过滤器里
                userRegisterBloomFilter.add(registerReqDTO.getUsername());
                try{
                    groupService.saveGroup(registerReqDTO.getUsername(),"默认分组");
                }catch (Exception e){
                    log.error("创建默认分组异常",e);
                }
                return;
            }
            //上锁是防止多个用户同时竞争一个用户名
            throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
        } catch (Exception e) {
            //没法捕捉sql异常?
            //处理布隆过滤器误判的情况
            log.warn("布隆过滤器发生误判");
            throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
        } finally {
            lock.unlock();
        }

    }

    @Override
    public void updateUser(UserUpdateReqDTO userUpdateReqDTO) {
        //TODO验证当前用户名是否为登录用户
        LambdaUpdateWrapper<UserDo> updateWrapper = new LambdaUpdateWrapper<UserDo>().eq(UserDo::getUsername, userUpdateReqDTO.getUsername());
        baseMapper.update(BeanUtil.convert(userUpdateReqDTO, UserDo.class), updateWrapper);
    }

    /**
     * 用户登录接口
     */
    @Override
    public UserLoginRespDTO userLogin(UserLoginReqDTO userLoginReqDTO) {
        LambdaQueryWrapper<UserDo> wrapper = new LambdaQueryWrapper<UserDo>()
                .eq(UserDo::getUsername, userLoginReqDTO.getUsername())
                .eq(UserDo::getPassword, userLoginReqDTO.getPassword())
                .eq(UserDo::getDelFlag, 0);
        UserDo userDo = baseMapper.selectOne(wrapper);
        //判断用户是否登录
        Boolean hasLogin = stringRedisTemplate.hasKey("login_" + userLoginReqDTO.getUsername());
        if (hasLogin != null && hasLogin) {
            //返回token,方便后续使用,仅作开发使用
            Set<Object> keys = stringRedisTemplate.opsForHash().keys("login_" + userLoginReqDTO.getUsername());
            return new UserLoginRespDTO(keys.iterator().next().toString());
        }
        //将uuid与redis中的json字符串建立对应关系，将其当作token返回
        return Optional.ofNullable(userDo).map(u -> {
            String uuid = UUID.randomUUID().toString();
            stringRedisTemplate.opsForHash().put("login_" + userLoginReqDTO.getUsername(), uuid, JSON.toJSONString(userDo));
            stringRedisTemplate.expire("login_" + userLoginReqDTO.getUsername(), 30L, TimeUnit.DAYS);
            return new UserLoginRespDTO(uuid);
        }).orElseThrow(() -> new ClientException("用户不存在或者密码错误"));
    }

    @Override
    public Boolean checkLogin(String username, String token) {
        return stringRedisTemplate.opsForHash().get("login_" + username, token) != null;
    }

    @Override
    public Boolean logOut(String username, String token) {
        if (checkLogin(username, token)) {
            stringRedisTemplate.delete("login_" + username);
            return true;
        }
        throw new ClientException("用户未登录");
    }
}
